Nel finire degli anni 50 l’economista della LSE A.W.Phillips osservò una relazione inversa fra disoccupazione e inflazione salariale, ovvero il tasso di crescita dei salari nominali, nell’economia britannica. Pochi anni dopo, Samuelson e Solow mostrarono che una simile relazione esisteva anche nell’economia americana, legando non più la disoccupazione solamente all’inflazione salariale, ma al tasso di crescita generale dei prezzi. La curva divenne così uno strumento utilizzato dai policy makers che, dopo aver valutato il trade-off tra inflazione e disoccupazione, mettevano in atto strategie atte a raggiungere un punto specifico della curva.
from config import*from config import make_df_ECB%matplotlib inline
Paper di Phillips
In questa sezione, replicheremo l’articolo originale di Phillips, utilizzando i dati forniti dalla Bank Of England (https://www.bankofengland.co.uk/statistics/research-datasets)
# Importa i dati sulla disoccupazione dal file Excel contenente dati macroeconomici del Regno Unitounemployment = pd.read_excel('a-millennium-of-macroeconomic-data-for-the-uk.xlsx', sheet_name='A50. Employment & unemployment', skiprows=2, usecols='A,J')# Rimuove le prime due righe che contengono informazioni non necessarieunemployment = unemployment[2:]# Rinomina la colonna non etichettata in 'Year' per maggiore chiarezzaunemployment.rename(columns={'Unnamed: 0': 'Year'}, inplace=True)# Imposta l'anno come indice del DataFrameunemployment.set_index('Year', inplace=True)# Converte la colonna del tasso di disoccupazione in formato numerico (float)unemployment['Unemployment rate'] = unemployment['Unemployment rate'].astype(float)# Mostra le prime righe del DataFrame per verificare la corretta importazione dei datiunemployment.head()
Unemployment rate
Year
1855
3.731877
1856
3.518180
1857
3.945176
1858
5.215345
1859
3.276380
# Importa i dati sui salari e l'inflazione dal file Excel contenente dati macroeconomici del Regno Unitowages_inflation = pd.read_excel('a-millennium-of-macroeconomic-data-for-the-uk.xlsx', sheet_name='A47. Wages and prices', usecols='A,BD')# Rinomina le colonne per una maggiore chiarezzawages_inflation.columns = ['Year', 'Wage index']# Rimuove le prime sei righe non necessariewages_inflation = wages_inflation[6:]# Converte la colonna 'Year' in formato interowages_inflation['Year'] = wages_inflation['Year'].astype(int)# Converte la colonna 'Wage index' in formato numerico (float)wages_inflation['Wage index'] = wages_inflation['Wage index'].astype(float)# Filtra i dati per includere solo gli anni a partire dal 1855wages_inflation = wages_inflation[wages_inflation['Year'] >=1855]# Imposta l'anno come indice del DataFramewages_inflation.set_index('Year', inplace=True)# Calcola la differenza centrale prima (approssimazione numerica della derivata prima) e la esprime in percentuale.# Questa è la misura utilizzata nell'articolo originale, qui utilizzeremo invece la variazione percentualewages_inflation['First central difference'] = ( (wages_inflation['Wage index'].shift(-1) - wages_inflation['Wage index'].shift(1)) / (2* wages_inflation['Wage index'])) *100# Calcola la variazione percentuale anno su anno del Wage indexwages_inflation['Percent change'] = wages_inflation['Wage index'].pct_change() *100# Mostra le prime righe del DataFrame per verificare la corretta elaborazione dei datiwages_inflation.head()
Wage index
First central difference
Percent change
Year
1855
59.0
NaN
NaN
1856
59.0
-1.694915
0.000000
1857
57.0
-2.631579
-3.389831
1858
56.0
0.000000
-1.754386
1859
57.0
1.754386
1.785714
# Unisce i DataFrame 'unemployment' e 'wages_inflation' utilizzando l'indice (Year) e rimuove eventuali valori NaNdf = pd.merge(unemployment, wages_inflation, right_index=True, left_index=True).dropna()# Suddivide il dataset in tre periodi storici distinti per analizzare la relazione tra disoccupazione e inflazione salarialedfA = df[df.index <=1913].copy() # Periodo pre-1913dfB = df[(df.index >=1913) & (df.index <=1948)].copy() # 1913-1948 (tra le due guerre)dfC = df[(df.index >=1948) & (df.index <=1957)].copy() # 1948-1957 (primi anni post-bellici)# Definisce le etichette temporali per i graficidates = ['1861-1913', '1913-1948', '1948-1957']# Crea una figura con tre subplot affiancati per rappresentare ciascun periodofig, ax = plt.subplots(1, 3, figsize=(10, 3.5))# Itera sui tre sotto-dataset per stimare e tracciare la curva di Phillips per ciascun periodofor i, _ inenumerate([dfA, dfB, dfC]):# Calcola il termine di aggiustamento per evitare problemi con il logaritmo a =abs(_["Percent change"].min()) +0.9# Trasforma in logaritmo il tasso di inflazione salariale e il tasso di disoccupazione _["Log wage inflation rate"] = np.log(_["Percent change"] + a) _["Log unemployment rate"] = np.log(_["Unemployment rate"])# Definisce le variabili per la regressione lineare (modello di Phillips) X = _["Log unemployment rate"] Y = _["Log wage inflation rate"] X = sm.add_constant(X) # Aggiunge l'intercetta al modello di regressione model = sm.OLS(Y, X).fit() # Stima il modello di regressione R2 = model.rsquared # Ottiene il coefficiente di determinazione R^2# Calcola i valori predetti dalla regressione e li riconverte dalla scala logaritmica _["Predicted log wage inflation rate"] = model.predict(X) _["Predicted wage inflation rate"] = np.exp(_["Predicted log wage inflation rate"]) - a# Ordina i dati per il tasso di disoccupazione per una migliore rappresentazione grafica sorted_data = _.sort_values(by='Unemployment rate')# Disegna una linea orizzontale per rappresentare l'asse y=0 ax[i].axhline(y=0, color='silver', linewidth=2)# Crea uno scatter plot con i dati osservati ax[i].scatter(_['Unemployment rate'], _['Percent change'], alpha=0.7, label="Osservazioni", color=bmh_colors[0])# Disegna la curva di Phillips stimata dalla regressione ax[i].plot(sorted_data['Unemployment rate'], sorted_data['Predicted wage inflation rate'], color=bmh_colors[1], label="Curva Di Phillips")# Imposta il titolo per ciascun sottografico ax[i].set_title(f"UK, {dates[i]}", loc='left')# Etichette degli assi ax[i].set_xlabel('Tasso Di Disoccupazione') ax[i].set_ylabel('Tasso Di Inflazione Salariale')# Attiva la griglia ax[i].grid(True)# Aggiunge la legenda ax[i].legend()# Migliora la disposizione dei grafici per evitare sovrapposizioniplt.tight_layout()
Paper di Samuelson e Solow
In questa sezione, cercheremo di analizzare il fenomeno all’interno dell’economia Americana, per la quale i dati sono purtroppo frammentari (si vedano https://data.bls.gov/pdq/SurveyOutputServlet e https://fred.stlouisfed.org/series/M08142USM055NNBR)
# Carica i dati sulla disoccupazione (BLS) dal file Excel, saltando le prime 16 righe e impostando 'Year' come indiceunemp = pd.read_excel('BLS1.xlsx', skiprows=16).set_index('Year')# Carica i dati sulle retribuzioni orarie medie da 25 industrie manifatturiere degli Stati Uniti (NBER)earnings = pd.read_csv('Average Hourly Earnings, Twenty-Five Manufacturing Industries for United States.csv').set_index('observation_date')# Converte l'indice di 'earnings' in formato datetime (anno-mese-giorno)earnings.index = pd.to_datetime(earnings.index, format='%Y-%m-%d')# Raggruppa i dati sulle retribuzioni annualmente, calcolando la media per ogni annoearnings = earnings.groupby(pd.Grouper(freq='Y')).mean()# Estrae solo l'anno dall'indice per facilitarne l'elaborazioneearnings.index = earnings.index.year# Filtra i dati per gli anni dal 1929 al 1947earnings = earnings[(earnings.index >=1929) & (earnings.index <=1947)]# Rinomina la colonna per avere una denominazione chiaraearnings.columns = ['HOURLY_WAGE']# Rinomina la colonna della disoccupazione per chiarezzaunemp.columns = ['UNEMPLOYMENT']# Unisce i due DataFrame sui rispettivi indici (anno)df = pd.merge(unemp, earnings, right_index=True, left_index=True)# Calcola la variazione percentuale annuale delle retribuzioni orariedf['PCT_CHANGE'] = df['HOURLY_WAGE'].pct_change() *100# Rimuove le righe con valori mancanti (NaN) creati durante il calcolo della variazione percentualedf = df.dropna()# Mostra le prime righe del DataFrame per verificare i datidf.head()
UNEMPLOYMENT
HOURLY_WAGE
PCT_CHANGE
Year
1930
8.7
0.589000
-0.113058
1931
15.9
0.563833
-4.272779
1932
23.6
0.497583
-11.749926
1933
24.9
0.490583
-1.406800
1934
21.7
0.579750
18.175641
# Calcola il termine di aggiustamento per evitare problemi con il logaritmoa =abs(df["PCT_CHANGE"].min()) +0.9# Calcola il logaritmo del tasso di variazione percentuale delle retribuzioni, aggiungendo il termine 'a'df["LOG_VAR_PERC"] = np.log(df["PCT_CHANGE"] + a)# Calcola il logaritmo del tasso di disoccupazionedf["LOG_UNRATE"] = np.log(df["UNEMPLOYMENT"])# Definisce le variabili per la regressione lineare (modello di Phillips)X = df["LOG_UNRATE"]Y = df["LOG_VAR_PERC"]X = sm.add_constant(X) # Aggiunge l'intercetta al modello di regressionemodel = sm.OLS(Y, X).fit() # Stima il modello di regressioneR2 = model.rsquared # Ottiene il coefficiente di determinazione R^2# Calcola i valori predetti dalla regressione e li riconverte dalla scala logaritmicadf["Predicted log inflation rate"] = model.predict(X)df["Predicted inflation rate"] = np.exp(df["Predicted log inflation rate"]) - a# Ordina i dati per il tasso di disoccupazione per una migliore rappresentazione graficasorted_data = df.sort_values(by='UNEMPLOYMENT')# Crea una figura con un solo sottograficofig, ax = plt.subplots(1, 1)# Disegna una linea orizzontale per rappresentare l'asse y=0ax.axhline(y=0, color='silver', linewidth=2)# Crea uno scatter plot con i dati osservatiax.scatter(df['UNEMPLOYMENT'], df['PCT_CHANGE'], alpha=0.7, label="Osservazioni", color=bmh_colors[0])# Disegna la curva di Phillips stimata dalla regressioneax.plot(sorted_data['UNEMPLOYMENT'], sorted_data['Predicted inflation rate'], color=bmh_colors[1], label="Curva Di Phillips")# Imposta il titolo del graficoax.set_title(f"US, 1930-1947", loc='left')# Etichette degli assiax.set_xlabel('Tasso Di Disoccupazione')ax.set_ylabel('Tasso Di Inflazione Salariale')# Attiva la grigliaax.grid(True)# Aggiunge la legendaax.legend()# Migliora la disposizione dei grafici per evitare sovrapposizioniplt.tight_layout()
E oggi?
def make_df_ECB(key, obs_name):"""Estrae i dati dal datawarehouse della BCE""" url_ ='https://sdw-wsrest.ecb.europa.eu/service/data/'# URL di base per il servizio web della BCE format_ ='?format=csvdata'# Impostazione del formato dei dati in CSV df = pd.read_csv(url_+key+format_) # Legge i dati CSV dal servizio web BCE usando la chiave df = df[['TIME_PERIOD', 'OBS_VALUE']] # Seleziona le colonne di interesse (periodo e valore osservato) df['TIME_PERIOD'] = pd.to_datetime(df['TIME_PERIOD']) # Converte il periodo in formato datetime df = df.set_index('TIME_PERIOD') # Imposta il periodo come indice del dataframe df.columns = [obs_name] # Rinomina la colonna con il nome della variabilereturn df# Chiave per il tasso di disoccupazioneunemp_rate_key ='LFSI/Q.I9.S.UNEHRT.TOTAL0.15_74.T'# Codice identificativo del tasso di disoccupazioneunemp_rate = make_df_ECB(unemp_rate_key, 'Unemployment Rate') # Ottieni i dati dal database ECB# Chiave per la wage inflation (inflazione salariale)wage_inflation_key ='MNA/Q.Y.I9.W2.S1.S1._Z.COM_PS._Z._T._Z.IX.V.N'# Codice identificativo del salario per addetto (indice)wage_inflation = make_df_ECB(wage_inflation_key, 'Compensation per employee') # Ottieni i dati dal database ECBwage_inflation = wage_inflation[wage_inflation.index >='2000-01-01'] # Filtra i dati dal 2000 in poi# Combina i dati di disoccupazione e salario in un unico dataframedf = pd.merge(unemp_rate, wage_inflation, right_index=True, left_index=True)# Raggruppa i dati per anno e calcola la media per il tasso di disoccupazione e l'ultimo valore per la compensazione per addettodf = df.groupby(pd.Grouper(freq='Y'))[['Unemployment Rate', 'Compensation per employee']].agg({'Unemployment Rate':'mean','Compensation per employee':'last'})df.index = df.index.year # Imposta l'indice come annodf['Wage inflation rate'] = df['Compensation per employee'].pct_change()*100# Calcola il tasso di inflazione salariale come variazione percentualedf = df.dropna() # Rimuove i valori mancantidf.head() # Mostra le prime righe del dataframe
Unemployment Rate
Compensation per employee
Wage inflation rate
TIME_PERIOD
2001
8.508061
70.332653
2.705440
2002
8.753020
72.127430
2.551840
2003
9.188271
73.757671
2.260224
2004
9.399281
75.327356
2.128164
2005
9.231180
77.193789
2.477762
# Calcola il termine di aggiustamento per evitare problemi con il logaritmoa =abs(df["Wage inflation rate"].min()) +0.9# Calcola il logaritmo del tasso di variazione percentuale delle retribuzioni, aggiungendo il termine 'a'df["LOG_VAR_PERC"] = np.log(df["Wage inflation rate"] + a)# Calcola il logaritmo del tasso di disoccupazionedf["LOG_UNRATE"] = np.log(df["Unemployment Rate"])# Definisce le variabili per la regressione lineare (modello di Phillips)X = df["LOG_UNRATE"]Y = df["LOG_VAR_PERC"]X = sm.add_constant(X) # Aggiunge l'intercetta al modello di regressionemodel = sm.OLS(Y, X).fit() # Stima il modello di regressioneR2 = model.rsquared # Ottiene il coefficiente di determinazione R^2# Calcola i valori predetti dalla regressione e li riconverte dalla scala logaritmicadf["Predicted log inflation rate"] = model.predict(X)df["Predicted inflation rate"] = np.exp(df["Predicted log inflation rate"]) - a# Ordina i dati per il tasso di disoccupazione per una migliore rappresentazione graficasorted_data = df.sort_values(by='Unemployment Rate')# Crea una figura con un solo sottograficofig, ax = plt.subplots(1, 1)# Disegna una linea orizzontale per rappresentare l'asse y=0ax.axhline(y=0, color='silver', linewidth=2)# Crea uno scatter plot con i dati osservatiax.scatter(df['Unemployment Rate'], df['Wage inflation rate'], alpha=0.7, label="Osservazioni", color=bmh_colors[0])# Disegna la curva di Phillips stimata dalla regressioneax.plot(sorted_data['Unemployment Rate'], sorted_data['Predicted inflation rate'], color=bmh_colors[1], label="Curva Di Phillips")# Imposta il titolo del graficoax.set_title(f"EU, 2001-2024", loc='left')# Etichette degli assiax.set_xlabel('Tasso Di Disoccupazione')ax.set_ylabel('Tasso Di Inflazione Salariale')# Attiva la grigliaax.grid(True)# Aggiunge la legendaax.legend()# Migliora la disposizione dei grafici per evitare sovrapposizioniplt.tight_layout()